
#include "RtBase.h"
#include "RtHttpHead.h"

#define MAX_CONTENT_LENGTH_DEFAULT (1024*1024*2) 

///////////////////////////////////////////////////////////////
// class CRtHttpHeaderArray
///////////////////////////////////////////////////////////////

LPCSTR CRtHttpHeaderArray::s_szHttpHeaderNewLine = "\r\n";

CRtHttpHeaderArray::CRtHttpHeaderArray()
	: m_checkContentLength(FALSE)
	, m_maxContentLength(MAX_CONTENT_LENGTH_DEFAULT)
{
}

CRtHttpHeaderArray::~CRtHttpHeaderArray()
{
}

BOOL CRtHttpHeaderArray::Clear()
{
	m_bHaveAllHeaders = FALSE;
	m_bHaveFirstLine = FALSE;
	m_Headers.clear();
	return TRUE;
}

BOOL CRtHttpHeaderArray::
PeekHeader(const CRtHttpAtom &aHeader) const
{
	CEntry *pEntry = NULL;
	LookupEntry(aHeader, pEntry);
	if (pEntry)
		return TRUE;
	else
		return FALSE;
}

BOOL CRtHttpHeaderArray::
SetHeader(const CRtHttpAtom &aHeader, const CRtString &aValue)
{
	CRtString strCopyValue(aValue);
	TrimString(strCopyValue, CRtIsSpace());
	if (strCopyValue.empty()) {
//		RT_WARNING_TRACE_THIS("CRtHttpHeaderArray:: aValue is empty!");
		CWbxHEntryVectorType::iterator iter = m_Headers.begin();
		for ( ; iter != m_Headers.end(); ++iter) {
			if (iter->m_atomHeader == aHeader) {
				m_Headers.erase(iter);
				break;
			}
		}
		return TRUE;
	}

	CEntry *pEntry = NULL;
	LookupEntry(aHeader, pEntry);

	if (!pEntry) {
		m_Headers.push_back(CEntry(aHeader, strCopyValue));
	}
	else if (CanAppendToHeader(aHeader)){
		if (aHeader == CRtHttpAtomList::Set_Cookie ||
            aHeader == CRtHttpAtomList::WWW_Authenticate ||
            aHeader == CRtHttpAtomList::Proxy_Authenticate)
            // Special case these headers and use a newline delimiter to
            // delimit the values from one another as commas may appear
            // in the values of these headers contrary to what the spec says.
            pEntry->m_strValue += '\n';
        else
            // Delimit each value from the others using a comma (per HTTP spec)
            pEntry->m_strValue += ", ";
        pEntry->m_strValue += strCopyValue;
	}
	else
		pEntry->m_strValue = strCopyValue;
	
	return TRUE;
}

BOOL CRtHttpHeaderArray::
GetHeader(const CRtHttpAtom &aHeader, CRtString &aValue) const
{
	CEntry *pEntry = NULL;
	LookupEntry(aHeader, pEntry);
	if (!pEntry)
		return FALSE;
	
	aValue = pEntry->m_strValue;
	return TRUE;
}

void CRtHttpHeaderArray::Flatten(CRtString &aFlattened) const
{
	CWbxHEntryVectorType::const_iterator iter = m_Headers.begin();
	for ( ; iter != m_Headers.end(); ++iter) {
		aFlattened += iter->m_atomHeader;
		aFlattened += ": ";
		aFlattened += iter->m_strValue;
		aFlattened += s_szHttpHeaderNewLine;
	}
}

void CRtHttpHeaderArray::
LookupEntry(const CRtHttpAtom &aHeader, CEntry *&aEntry) const
{
	aEntry = NULL;
	CWbxHEntryVectorType::const_iterator iter = m_Headers.begin();
	for ( ; iter != m_Headers.end(); ++iter) {
		if (iter->m_atomHeader == aHeader) {
			aEntry = const_cast<CEntry *>(&(*iter));
			break;
		}
	}
}

BOOL CRtHttpHeaderArray::
CanAppendToHeader(const CRtHttpAtom &aHeader) const
{
    return (aHeader == CRtHttpAtomList::Accept_Charset ||
           aHeader == CRtHttpAtomList::Content_Type ||
		   aHeader == CRtHttpAtomList::Content_Length ||
           aHeader == CRtHttpAtomList::User_Agent ||
           aHeader == CRtHttpAtomList::Referer ||
           aHeader == CRtHttpAtomList::Host ||
           aHeader == CRtHttpAtomList::Authorization ||
           aHeader == CRtHttpAtomList::Proxy_Authorization ||
           aHeader == CRtHttpAtomList::If_Modified_Since ||
           aHeader == CRtHttpAtomList::If_Unmodified_Since ||
           aHeader == CRtHttpAtomList::From ||
           aHeader == CRtHttpAtomList::Location ||
           aHeader == CRtHttpAtomList::Max_Forwards
		   ) ? FALSE : TRUE;
}

BOOL CRtHttpHeaderArray::
ParseHeaderLine(LPCSTR aLine, CRtHttpAtom *aAtom, CRtString *aValue)
{
	LPCSTR pPtr = strchr(aLine, ':');

	// the header is malformed... but, there are malformed headers in the
    // world.  search for ' ' and '\t' to simulate 4.x/IE behavior.
    if (!pPtr) {
        pPtr = strchr(aLine, ' ');
        if (!pPtr) {
            pPtr = strchr(aLine, '\t');
            if (!pPtr) {
                // some broken cgi scripts even use '=' as a delimiter!!
                pPtr = strchr(aLine, '=');
            }
        }
    }

	BOOL rv = TRUE;
	if (!pPtr || pPtr <= aLine)
		return rv;

	CRtHttpAtom atom;
	CRtString strValue;
	CRtString strHead(aLine, pPtr - aLine);
	atom = CRtHttpAtomList::Instance()->ResolveAtom(strHead);
	if (atom) {
		strValue = pPtr + 1;
		rv = SetHeader(atom, strValue);
	}

	if (aAtom)
		*aAtom = atom;
	if (aValue)
		*aValue = strValue;
	return rv;
}

BOOL CRtHttpHeaderArray::ParseLine(const CRtString &aLine)
{
	BOOL rv = FALSE;
	RT_ASSERTE_RETURN(!m_bHaveAllHeaders, TRUE);

	CRtString::size_type nLen = aLine.length();
	if (nLen < 2 || aLine[nLen-1] != '\n' || aLine[nLen-2] != '\r') {
		RT_ERROR_TRACE_THIS("CRtHttpHeaderArray::ParseLine, not a line. aLine=" << aLine);
		return rv;
	}

	CRtString strLineContent(aLine);
	strLineContent.resize(nLen - 2);
	if (strLineContent.empty()) {
		if (!m_bHaveFirstLine) {
			RT_ERROR_TRACE_THIS("CRtHttpHeaderArray::ParseLine, no first line!");
			return FALSE;
		}
		if (m_checkContentLength)
		{
			LONG contentLength = GetContentLength();
			if (contentLength>m_maxContentLength)
			{
				RT_WARNING_TRACE_THIS("CRtHttpHeaderArray::ParseLine, content too long! length="<<contentLength<<" max="<<m_maxContentLength);
				return FALSE;
			}
		}
		m_bHaveAllHeaders = TRUE;
		rv = TRUE;
	} else if (m_bHaveFirstLine){
		rv = ParseHeaderLine(strLineContent.c_str());
	} else {
		rv = ParseFirstLine_t(strLineContent.c_str());
		if (rv)
			m_bHaveFirstLine = TRUE;
	}
	return rv;
}

BOOL CRtHttpHeaderArray::ParseFirstLine_t(LPCSTR aLine)
{
	RT_ASSERTE(!"CRtHttpHeaderArray::ParseFirstLine_t");
	return FALSE;
}

LONG CRtHttpHeaderArray::GetContentLength()
{
	CRtString strLength;
	BOOL rv = CRtHttpHeaderArray::GetHeader(
		CRtHttpAtomList::Content_Length, 
		strLength);
	if (rv)
		return atoi(strLength.c_str());
	else
		return -1;
}


///////////////////////////////////////////////////////////////
// class CRtHttpRequestHead
///////////////////////////////////////////////////////////////

CRtHttpRequestHead::CRtHttpRequestHead()
{
	Clear();
}

CRtHttpRequestHead::~CRtHttpRequestHead()
{
}

CRtString CRtHttpRequestHead::Flatten() const
{
	CRtString strFlattened;
	strFlattened.reserve(256);

	strFlattened += m_atMethod.get();
	strFlattened += ' ';
	strFlattened += m_strRequestURI;
	strFlattened += " HTTP/";
	switch (m_nVersion) {
	case HttpVersion_1_0:
		strFlattened += "1.0";
		break;
	case HttpVersion_1_1:
		strFlattened += "1.1";
		break;
	default:
		RT_ASSERTE(FALSE);
		break;
	}
	strFlattened += s_szHttpHeaderNewLine;

	CRtHttpHeaderArray::Flatten(strFlattened);
	return strFlattened;
}

BOOL CRtHttpRequestHead::SetMethod(const CRtHttpAtom &aAtom)
{
	BOOL rv = FALSE;
	if (aAtom == CRtHttpAtomList::Options 
		|| aAtom == CRtHttpAtomList::Head
		|| aAtom == CRtHttpAtomList::Post
		|| aAtom == CRtHttpAtomList::Put
		|| aAtom == CRtHttpAtomList::Get
		|| aAtom == CRtHttpAtomList::Index
		|| aAtom == CRtHttpAtomList::Delete
		|| aAtom == CRtHttpAtomList::Trace
		|| aAtom == CRtHttpAtomList::Connect
		|| aAtom == CRtHttpAtomList::M_Post)
	{
		m_atMethod = aAtom;
		rv = TRUE;
	}
	return rv;
}

BOOL CRtHttpRequestHead::Clear()
{
	m_atMethod = NULL;
	m_nVersion = HttpVersion_Unknow;
	m_strRequestURI.resize(0);
	return CRtHttpHeaderArray::Clear();
}

BOOL CRtHttpRequestHead::ParseFirstLine_t(LPCSTR aLine)
{
	LPCSTR pszStart = aLine;
	LPCSTR pszMove = ::strchr(pszStart, ' ');
	if (pszMove) {
		CRtString strMethod(pszStart, pszMove);
		m_atMethod = CRtHttpAtomList::Instance()->ResolveAtom(strMethod);
		if (m_atMethod==CRtHttpAtomList::Post)
			m_checkContentLength = TRUE;
	}
	else
		m_atMethod = NULL;
	if (!m_atMethod) {
		RT_ERROR_TRACE_THIS("CRtHttpRequestHead::ParseFirstLine_t,"
			" unknow method. aLine=" << aLine);
		return FALSE;
	}

	RT_ASSERTE(pszMove);
	pszMove++;
	pszStart = pszMove;
	pszMove = ::strchr(pszStart, ' ');

	m_strRequestURI.resize(0);
	if (pszMove) 
		m_strRequestURI.assign(pszStart, pszMove);
	if (m_strRequestURI.empty()) {
		RT_ERROR_TRACE_THIS("CRtHttpRequestHead::ParseFirstLine_t,"
			" unknow method. aLine=" << aLine);
		return FALSE;
	}
	
	pszMove++;
	pszStart = pszMove;
	if (!strcasecmp(pszStart, "HTTP/1.1"))
		m_nVersion = HttpVersion_1_1;
	else if (!strcasecmp(pszStart, "HTTP/1.0"))
		m_nVersion = HttpVersion_1_0;
	else {
		RT_ERROR_TRACE_THIS("CRtHttpRequestHead::ParseFirstLine_t,"
			" unknow HTTP version. aLine=" << aLine);
		return FALSE;
	}

	return TRUE;
}

///////////////////////////////////////////////////////////////
// class CRtHttpResponseHead
///////////////////////////////////////////////////////////////

CRtHttpResponseHead::CRtHttpResponseHead()
{
	Clear();
}

CRtHttpResponseHead::~CRtHttpResponseHead()
{

}

CRtString CRtHttpResponseHead::Flatten() const
{
	CRtString strFlattened;
	strFlattened.reserve(256);

	strFlattened += "HTTP/";
	switch (m_nVersion) {
	case HttpVersion_1_0:
		strFlattened += "1.0 ";
		break;
	case HttpVersion_1_1:
		strFlattened += "1.1 ";
		break;
	default:
		RT_ASSERTE(FALSE);
		break;
	}

	char szNum[16];
	sprintf(szNum, "%d", m_nStatus);
	strFlattened += szNum;
	strFlattened += ' ';
	strFlattened += m_strStatusText;
	strFlattened += s_szHttpHeaderNewLine;

	CRtHttpHeaderArray::Flatten(strFlattened);
	return strFlattened;
}

BOOL CRtHttpResponseHead::Clear()
{
	m_nStatus = -1;
	m_nVersion = HttpVersion_Unknow;
	m_strStatusText.resize(0);
	m_strContentType.resize(0);
	return CRtHttpHeaderArray::Clear();
}

BOOL CRtHttpResponseHead::ParseFirstLine_t(LPCSTR aLine)
{
	LPCSTR pHttp = "HTTP";
	DWORD dwLen = strlen(pHttp);
	if (strncasecmp(aLine, pHttp, dwLen) != 0) {
		RT_ERROR_TRACE_THIS("CRtHttpResponseHead::ParseFirstLine_t,"
			" no HTTP ahead. aLine=" << aLine);
		return FALSE;
	}
	aLine += dwLen;
	
	if (*aLine++ != '/') {
		m_nVersion = HttpVersion_1_0;
	} else {
		if (strncmp(aLine, "1.0", 3) == 0)
			m_nVersion = HttpVersion_1_0;
		else if (strncmp(aLine, "1.1", 3) == 0)
			m_nVersion = HttpVersion_1_1;
		else {
			RT_ERROR_TRACE_THIS("CRtHttpResponseHead::ParseFirstLine_t,"
				" unknow HTTP version. aLine=" << aLine);
			return FALSE;
		}
		aLine += 4;
	}
	
	m_nStatus = atoi(aLine);
	aLine = strchr(aLine, ' ');
	if (!aLine)
		m_strStatusText = "OK";
	else {
		m_strStatusText = ++aLine;
	}
	return TRUE;
}

LONG CRtHttpResponseHead::GetContentLength()
{
	switch (GetStatus()) {
	case 204:
	case 205:
	case 304:
		// these statuses are no content.
		return 0;
	default: 
		return CRtHttpHeaderArray::GetContentLength();
	}
}
